﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Configuration;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Threading;
using System.Globalization;

using LongNet.Util;
namespace LongNet.Util
{
    public sealed class LogManager
    {
        #region Fields

        // default logger
        private static Logger logger = null;
        // global settings
        private static Settings settings = new Settings();

        // lock object
        private static object syncRoot = new object();
        // other named logger instances
        private static Dictionary<string, Logger> loggerInstances = new Dictionary<string, Logger>();

        #endregion

        #region Properties

        public static Logger Default { get { return logger; } }

        #endregion

        #region Constructor

        static LogManager()
        {
            Utils.DoTryCatch(() => settings.LogLevel = LogLevel.FromString(GetSetting("LongNet.LogLevel")??"Debug"));

            settings.LogToConsole = Utils.StringToBool(GetSetting("LongNet.LogToConsole")??"true");
            settings.LogToFile = Utils.StringToBool(GetSetting("LongNet.LogToFile")??"true");
            string folder = GetSetting("LongNet.LogFolder"??System.IO.Directory.GetCurrentDirectory());
            try
            {
                settings.LogFolder = Utils.GetFullPath(folder);

                var dir = new DirectoryInfo(settings.LogFolder);
                if (!dir.Exists)
                {
                    dir.Create();
                }
            }
            catch (Exception ex)
            {
                settings.LogToFile = false;
                throw ex;
            }

            logger = new Logger("Default");
        }

        private static string GetSetting(string configName)
        {
            return ConfigurationManager.AppSettings[configName];
        }

        #endregion

        #region Static Methods

        public static Logger GetLogger(Type type)
        {
            return GetLogger(type.FullName);
        }

        public static Logger GetLogger(string name)
        {
            Logger logger;
            if (loggerInstances.TryGetValue(name, out logger))
            {
                return logger;
            }
            else
            {
                loggerInstances[name] = new Logger(name);
                return loggerInstances[name];
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static Logger GetCurrentClassLogger()
        {
            StackFrame frame = new StackFrame(1, false);
            return GetLogger(frame.GetMethod().DeclaringType.FullName);
        }

        #endregion

        #region Logger

        public class Logger
        {
            #region Fields

            private string name = string.Empty;
            public static readonly string Indent = "   ";
            public static readonly ConsoleColor[] ConsoleColors = { ConsoleColor.White, ConsoleColor.Green, ConsoleColor.Yellow, ConsoleColor.Red, ConsoleColor.DarkRed };

            #endregion

            #region Properties

            public string Name { get { return this.name; } }
            public LogLevel LogLevel { get { return settings.LogLevel; } }
            public bool LogToConsole { get { return settings.LogToConsole; } }
            public bool LogToFile { get { return settings.LogToFile; } }
            public string LogFolder { get { return settings.LogFolder; } }

            public bool IsDebugEnabled { get { return settings.IsDebugEnabled; } }
            public bool IsInfoEnabled { get { return settings.IsInfoEnabled; } }
            public bool IsWarnEnabled { get { return settings.IsWarnEnabled; } }
            public bool IsErrorEnabled { get { return settings.IsErrorEnabled; } }
            public bool IsFatalEnabled { get { return settings.IsFatalEnabled; } }

            #endregion

            #region Constructors

            internal Logger(string name)
            {
                this.name = name;

                if (IsDebugEnabled)
                    Debug(name + " Logger initialized.");
            }

            #endregion

            #region Methods

            public void Write(LogLevel level, IFormatProvider formatProvider, Exception exception, string message, object[] args)
            {
                if (level < settings.LogLevel)
                    return;

                if (!settings.LogToFile && !settings.LogToConsole)
                    return;

                // 不记录 Response.End() 或 response.Redirect() 在 try-catch 里引发的异常
                if (exception != null && exception.GetType() == typeof(System.Threading.ThreadAbortException))
                    return;

                try
                {
                    string formattedMessage = message;
                    if (message == null)
                        formattedMessage = string.Empty;
                    else if (args != null && args.Length > 0)
                        formattedMessage = string.Format(formatProvider, message, args);

                    var exceptionMessage = new StringBuilder();
                    if (exception != null)
                    {
                        string messageIndent = Indent;
                        string stackTraceIndent = string.Empty;
                        do
                        {
                            exceptionMessage.AppendFormat(
                                "{0}{1}{2}{3}",
                                Environment.NewLine + messageIndent,
                                exception.Message == null ? string.Empty : exception.Message.Replace(Environment.NewLine, string.Empty),
                                Environment.NewLine + stackTraceIndent,
                                exception.StackTrace == null ? string.Empty : exception.StackTrace.Replace(Environment.NewLine, Environment.NewLine + stackTraceIndent));

                            messageIndent += Indent;
                            stackTraceIndent += Indent;
                            exception = exception.InnerException;
                        }
                        while (exception != null);
                    }

                    var now = DateTime.Now;

                    string logMessage = string.Format(
                        "{0} {1} <{2}> {3} {4}",
                        now.ToString("yyyy-MM-dd HH:mm:ss.ffff", CultureInfo.InvariantCulture),
                        level.ToString().PadRight(6, ' '),
                        name,
                        formattedMessage,
                        exceptionMessage.ToString());

                    // Console
                    if (settings.LogToConsole)
                    {
                        lock (syncRoot)
                        {
                            Console.ForegroundColor = ConsoleColors[level.Ordinal];
                            Console.WriteLine(logMessage);
                            Console.ResetColor();
                        }
                    }

                    // File
                    if (settings.LogToFile)
                    {
                        lock (syncRoot)
                        {
                            string file = string.Concat(
                                settings.LogFolder,
                                "\\",
                                now.ToString("yyyy-MM-dd"),
                                ".config");

                            using (var textWriter = File.AppendText(file))
                            {
                                textWriter.WriteLine(logMessage);
                                textWriter.Close();
                            }
                            //using (var fs = new FileStream(file, FileMode.Append, FileAccess.Write, FileShare.Write))
                            //{
                            //    byte[] text = Encoding.UTF8.GetBytes(logMessage + Environment.NewLine);
                            //    fs.Write(text, 0, text.Length);
                            //    fs.Close();
                            //}
                        }
                    }
                }
                catch (Exception)
                {
                    // we have no place to log the message to so we ignore it
                }
            }

            public void Log(LogLevel level, Exception exception)
            {
                Write(level, null, exception, null, null);
            }
            public void Log(LogLevel level, string message)
            {
                Write(level, null, null, message, null);
            }
            public void Log(LogLevel level, Exception exception, string message)
            {
                Write(level, null, exception, message, null);
            }
            public void Log(LogLevel level, string message, params object[] args)
            {
                Write(level, null, null, message, args);
            }
            public void Log(LogLevel level, Exception exception, string message, params object[] args)
            {
                Write(level, null, exception, message, args);
            }
            public void Log(LogLevel level, IFormatProvider formatProvider, string message, params object[] args)
            {
                Write(level, formatProvider, null, message, args);
            }
            public void Log(LogLevel level, IFormatProvider formatProvider, Exception exception, string message, params object[] args)
            {
                Write(level, formatProvider, exception, message, args);
            }

            public void Debug(Exception exception)
            {
                Write(LogLevel.Debug, null, exception, null, null);
            }
            public void Debug(string message)
            {
                Write(LogLevel.Debug, null, null, message, null);
            }
            public void Debug(Exception exception, string message)
            {
                Write(LogLevel.Debug, null, exception, message, null);
            }
            public void Debug(string message, params object[] args)
            {
                Write(LogLevel.Debug, null, null, message, args);
            }
            public void Debug(Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Debug, null, exception, message, args);
            }
            public void Debug(IFormatProvider formatProvider, string message, params object[] args)
            {
                Write(LogLevel.Debug, formatProvider, null, message, args);
            }
            public void Debug(IFormatProvider formatProvider, Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Debug, formatProvider, exception, message, args);
            }

            public void Info(Exception exception)
            {
                Write(LogLevel.Info, null, exception, null, null);
            }
            public void Info(string message)
            {
                Write(LogLevel.Info, null, null, message, null);
            }
            public void Info(Exception exception, string message)
            {
                Write(LogLevel.Info, null, exception, message, null);
            }
            public void Info(string message, params object[] args)
            {
                Write(LogLevel.Info, null, null, message, args);
            }
            public void Info(Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Info, null, exception, message, args);
            }
            public void Info(IFormatProvider formatProvider, string message, params object[] args)
            {
                Write(LogLevel.Info, formatProvider, null, message, args);
            }
            public void Info(IFormatProvider formatProvider, Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Info, formatProvider, exception, message, args);
            }

            public void Warn(Exception exception)
            {
                Write(LogLevel.Warn, null, exception, null, null);
            }
            public void Warn(string message)
            {
                Write(LogLevel.Warn, null, null, message, null);
            }
            public void Warn(Exception exception, string message)
            {
                Write(LogLevel.Warn, null, exception, message, null);
            }
            public void Warn(string message, params object[] args)
            {
                Write(LogLevel.Warn, null, null, message, args);
            }
            public void Warn(Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Warn, null, exception, message, args);
            }
            public void Warn(IFormatProvider formatProvider, string message, params object[] args)
            {
                Write(LogLevel.Warn, formatProvider, null, message, args);
            }
            public void Warn(IFormatProvider formatProvider, Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Warn, formatProvider, exception, message, args);
            }

            public void Error(Exception exception)
            {
                Write(LogLevel.Error, null, exception, null, null);
            }
            public void Error(string message)
            {
                Write(LogLevel.Error, null, null, message, null);
            }
            public void Error(Exception exception, string message)
            {
                Write(LogLevel.Error, null, exception, message, null);
            }
            public void Error(string message, params object[] args)
            {
                Write(LogLevel.Error, null, null, message, args);
            }
            public void Error(Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Error, null, exception, message, args);
            }
            public void Error(IFormatProvider formatProvider, string message, params object[] args)
            {
                Write(LogLevel.Error, formatProvider, null, message, args);
            }
            public void Error(IFormatProvider formatProvider, Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Error, formatProvider, exception, message, args);
            }

            public void Fatal(Exception exception)
            {
                Write(LogLevel.Fatal, null, exception, null, null);
            }
            public void Fatal(string message)
            {
                Write(LogLevel.Fatal, null, null, message, null);
            }
            public void Fatal(Exception exception, string message)
            {
                Write(LogLevel.Fatal, null, exception, message, null);
            }
            public void Fatal(string message, params object[] args)
            {
                Write(LogLevel.Fatal, null, null, message, args);
            }
            public void Fatal(Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Fatal, null, exception, message, args);
            }
            public void Fatal(IFormatProvider formatProvider, string message, params object[] args)
            {
                Write(LogLevel.Fatal, formatProvider, null, message, args);
            }
            public void Fatal(IFormatProvider formatProvider, Exception exception, string message, params object[] args)
            {
                Write(LogLevel.Fatal, formatProvider, exception, message, args);
            }

            #endregion
        }

        #endregion

        #region Logger Settings

        internal class Settings
        {
            public bool LogToFile = false;
            public bool LogToConsole = false;
            public string LogFolder = string.Empty;

            private LogLevel logLevel = LogLevel.Off;
            private bool isDebugEnabled = false;
            private bool isInfoEnabled = false;
            private bool isWarnEnabled = false;
            private bool isErrorEnabled = false;
            private bool isFatalEnabled = false;

            public LogLevel LogLevel
            {
                get { return logLevel; }
                set
                {
                    logLevel = value;
                    isDebugEnabled = LogLevel.Debug >= logLevel;
                    isInfoEnabled = LogLevel.Info >= logLevel;
                    isWarnEnabled = LogLevel.Warn >= logLevel;
                    isErrorEnabled = LogLevel.Error >= logLevel;
                    isFatalEnabled = LogLevel.Fatal >= logLevel;
                }
            }

            public bool IsDebugEnabled { get { return isDebugEnabled; } }
            public bool IsInfoEnabled { get { return isInfoEnabled; } }
            public bool IsWarnEnabled { get { return isWarnEnabled; } }
            public bool IsErrorEnabled { get { return isErrorEnabled; } }
            public bool IsFatalEnabled { get { return isFatalEnabled; } }
        }

        #endregion

    }
}
